﻿using System;
using Magellan.Routing.Tests.Extensions;
using NUnit.Framework;

namespace Magellan.Routing.Tests.Behavior
{
    [TestFixture]
    public class RoutingBehavior
    {
        protected RouteCollection Routes { get; set; }

        [SetUp]
        public void SetUp()
        {
            Routes = new RouteCollection();   
        }

        [Test]
        public void ShouldMatchSingleStatic()
        {
            Routes.Register("Home", "home");
            Routes.MatchPathToRoute("home").AssertRoute("Home");
        }

        [Test]
        public void ShouldMatchSingleVariable()
        {
            Routes.Register("Controller", "{controller}");
            Routes.MatchPathToRoute("blog").AssertRoute("Controller", controller => "blog");
        }

        [Test]
        public void ShouldMatchStaticVariable()
        {
            Routes.Register("BlogTitle", "blog/{title}");
            Routes.MatchPathToRoute("blog/wpf").AssertRoute("BlogTitle", title => "wpf");
        }

        [Test]
        public void ShouldMatchStaticVariableVariable()
        {
            Routes.Register("WikiTitleRev", "wiki/{title}/{rev}");
            Routes.MatchPathToRoute("wiki/wpf/1").AssertRoute("WikiTitleRev", title => "wpf", rev => "1");
        }

        [Test]
        public void ShouldMatchDefaultedParameters()
        {
            Routes.Register("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional } );
            Routes.MatchPathToRoute("").AssertRoute("Default",  controller => "Home", action => "Index", id => null);
            Routes.MatchPathToRoute("foo").AssertRoute("Default", controller => "foo", action => "Index", id => null);
            Routes.MatchPathToRoute("foo/bar").AssertRoute("Default", controller => "foo", action => "bar", id => null);
            Routes.MatchPathToRoute("foo/bar/1").AssertRoute("Default", controller => "foo", action => "bar", id => "1");
        }

        [Test]
        public void ShouldMatchUsingConstraints()
        {
            Routes.Register("BlogById", "blog/{id}", new { }, new { id = "^[0-9]+$" });
            Routes.Register("BlogByTitle", "blog/{title}");
            Routes.MatchPathToRoute("blog/1").AssertRoute("BlogById", id => "1");
            Routes.MatchPathToRoute("blog/12").AssertRoute("BlogById", id => "12");
            Routes.MatchPathToRoute("blog/foobar").AssertRoute("BlogByTitle", title => "foobar");
        }

        [Test]
        public void ShouldMatchCatchAll()
        {
            Routes.Register("BlogById", "blog/{id}/{*foo}");
            Routes.MatchPathToRoute("blog/1").AssertRoute("BlogById", id => "1");
            Routes.MatchPathToRoute("blog/42/a/b/c").AssertRoute("BlogById", id => "42", foo => "a/b/c");
        }

        [Test]
        public void ShouldCreatePathFromRoute()
        {
            Routes.Register("Default", "{controller}/{action}/{id}", new { id = "" });
            Routes.MatchRouteToPath(new { controller = "Foo", action = "Bar" }).AssertPath("Default", "Foo/Bar");
            Routes.MatchRouteToPath(new { controller = "Foo", action = "Bar", id = "1" }).AssertPath("Default", "Foo/Bar/1");
        }

        [Test]
        public void UnmatchedRoutesShouldProduceNiceErrorMessage()
        {
            Routes.Register("BlogById", "blog/{id}", new { }, new { id = "^[0-9]+$" });
            Routes.Register("WikiById", "wiki/{id}", new { }, new { id = "^[0-9]+$" });
            var match = Routes.MatchPathToRoute("wiki/bambam");
            Assert.IsFalse(match.Success);
            Assert.AreEqual(
                "Route 'BlogById' did not match: Expected segment 'blog'; got 'wiki'." + Environment.NewLine +
                "Route 'WikiById' did not match: Segment 'bambam' did not match constraint for parameter 'id'." + Environment.NewLine, 
                match.FailReason);
        }

        [Test]
        public void DuplicateRoutesByNameAreNotAllowed()
        {
            Routes.Register("R1", "blog/{id}");
            Routes.Register("R2", "blog/{id}");
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "blog/{title}"));          // Name is duplicated
        }

        [Test]
        public void RoutePathFormatsAreVeryForgiving()
        {
            // In ASP.NET route formats cannot start with slashes and have a few other limitations. In the Magellan
            // implementation it would be harder to enforce this, and as it really doesn't buy anything, there's no 
            // need to - let's be forgiving of format inconsistencies.
            Routes.Register("R0", null);
            Routes.Register("R1", "");
            Routes.Register("R2", "/");
            Routes.Register("R3", "abc");
            Routes.Register("R4", "abc/");
            Routes.Register("R5", "/abc");
            Routes.Register("R6", "/abc/");
            Routes.Register("R7", "/abc/def");
            Routes.Register("R8", "/abc////def");
            Routes.Register("R9", "/abc/\\\\\\def");
        }

        [Test]
        public void ValidationShouldPickUpCommonRouteErrorsAtRegistration()
        {
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "blog/{id}/{id}"));        // Same parameter name used
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "blog/{ID}/{*id}"));
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "blog/{*Foo}/{*foo}"));    
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "blog/{*foo}/{*bar}"));    // Multiple catch-all's
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "blog/{*path}/hello"));    // Catch-all not at end of path
            Assert.Throws<InvalidRouteException>(() => Routes.Register("R1", "{sddsds/foosdd}"));       // Nonsense path
        }

        [Test]
        public void ShouldFailOnLongerRoutes()
        {
            Routes.Register("BlogById", "blog/{id}", new { }, new { id = "^[0-9]+$" });
            var match = Routes.MatchPathToRoute("blog/1/2/3/4/5/6");
            Assert.IsFalse(match.Success);
            Assert.AreEqual(
                "Route 'BlogById' did not match: Route was initially matched, but the request contains additional unexpected segments." + Environment.NewLine,
                match.FailReason);
        }
    }
}
